package com.zuijin.vue2blog.common.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.InvalidClaimException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.lettuce.core.output.ListOfGenericMapsOutput;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * ClassName:    JwtUtil
 * CreateBy:     IntelliJ IDEA
 * Author:       醉瑾
 * Date:         2022-04-19
 * Description : JwtToken 生成工具
 */
// https://baobao555.tech/archives/40    https://blog.csdn.net/qq_44194174/article/details/121517575

/*
 * JWT 组成部分
 * 1.Header(头） 作用：记录令牌类型、签名算法等 例如：{“alg":"HS256","type","JWT}
 * 2.Payload(有效载荷）作用：携带一些用户信息 例如{"userId":"1","username":"mayikt"}
 * 3.Signature(签名）作用：防止Token被篡改、确保安全性 例如 计算出来的签名，一个字符串
 * */
@Component
public class JwtUtil {
    // 密钥
    @Value("${jwt.secretKey}") // 取不到值的情况 https://www.jb51.net/article/229558.htm
    private String secretKey;

    // 超时时间
    @Value("${jwt.expirationTime}")
    private Long expirationTime;

    // JWT Claims(声明) https://www.cnblogs.com/hnxxcxg/p/11367704.html
    /*
     *   “iss” (issuer)      发行人
     *   “sub” (subject)     主题
     *   “aud” (audience)    接收方 用户
     *   “exp” (expiration   time) 到期时间
     *   “nbf” (not before)  在此之前不可用
     *   “iat” (issued at)   jwt的签发时间
     *   “jti” (JWT ID)      jwt的唯一身份标识，主要用来作为一次性token,从而回避重放攻击。
     * */
    public String generateToken(Map<String, Object> claims) {
        String token = JWT.create()
                .withPayload(claims)
                .sign(Algorithm.HMAC256(secretKey));    // 使用算法
        return token;
    }


    /**
     * 根据用户信息生成 token
     *
     * @param userDetails 用户信息
     * @return
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>() {{
            put("sub", userDetails.getUsername()); // 用户名
            put("iat", new Date()); // 签发时间
            put("exp", new Date(System.currentTimeMillis() + expirationTime)); // 过期时间
        }};
        return generateToken(claims);
    }

    /**
     * 验证 token
     *
     * @param token
     * @return
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        try {
            DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(secretKey))
                    .acceptLeeway(1)            // 指定超时多少秒后 token 依然有效 nbf,iat,exp
                    .acceptExpiresAt(2)  // 指定 日期的自定义值（会覆盖上面的）
                    .build()
                    .verify(token);
            String username = userDetails.getUsername();
            return username.equals(decodedJWT.getClaim("sub").asString());
        } catch (AlgorithmMismatchException e) {
            System.out.println("算法不一致");
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            System.out.println("签名无效");
        } catch (TokenExpiredException e) {
            System.out.println("token已过期");
        } catch (InvalidClaimException e) {
            System.out.println("包含未定义值");
        }
        return false;
    }


    public String getUsernameFromToken(String token) {
        // 此方法不会验证令牌签名，直接解码，解码出错直接报错
        // TODO
        return JWT.decode(token).getClaim("sub").asString();
    }


    /**
     * 检测 token 是否过期
     *
     * @param token
     * @return
     */
    public Map<String, Claim> isTokenExpired(String token) {
        return JWT.require(Algorithm.HMAC256(secretKey))
                .build()
                .verify(token)
                .getClaims();
    }

    /**
     * 刷新 token
     *
     * @param oldToken
     * @return
     */
    public String refreshToken(String oldToken) {
        return null;
    }
}
